package dao

import (
	"context"
	"errors"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

type ArticleDao interface {
	Insert(ctx context.Context, art Article) (int64, error)
	UpdateById(ctx context.Context, art Article) error
	//同步线上库
	Upsert(ctx context.Context, art PublishedArticle) error
	//统一步骤
	Sync(ctx context.Context, art Article) (int64, error)
	SyncStatus(ctx context.Context, art Article) error
	GetByAuthor(ctx context.Context, author int64, offset, limit int) ([]Article, error)
	GetById(ctx context.Context, id int64) (Article, error)
	GetPublishById(ctx context.Context, id int64) (PublishedArticle, error)
	ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]PublishedArticle, error)
}
type GormArticleDao struct {
	db *gorm.DB
}

func NewGormArticleDao(db *gorm.DB) *GormArticleDao {
	return &GormArticleDao{db: db}
}
func (dao *GormArticleDao) Insert(ctx context.Context, art Article) (int64, error) {
	now := time.Now().UnixMilli()
	art.Ctime = now
	art.Utime = now
	err := dao.db.WithContext(ctx).Create(&art).Error
	return art.Id, err
}
func (dao *GormArticleDao) UpdateById(ctx context.Context, art Article) error {
	now := time.Now().UnixMilli()
	art.Utime = now
	res := dao.db.WithContext(ctx).Model(&art).
		Where("id=? and author_id=?", art.Id, art.AuthorId).
		Updates(map[string]any{
			"title":   art.Title,
			"content": art.Content,
			"utime":   art.Utime,
			"status":  art.Status,
		})
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return fmt.Errorf("更新失败，可能是创作者非法 id %d,author_id %d",
			art.Id, art.AuthorId)
	}
	return res.Error
}

func (dao *GormArticleDao) SyncStatus(ctx context.Context, art Article) error {
	now := time.Now().UnixMilli()
	return dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		res := tx.Model(&PublishedArticle{}).
			Where("id=? and author_id=?", art.Id, art.AuthorId).
			Updates(map[string]any{
				"status": art.Status,
				"utime":  now,
			})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected != 1 {
			return fmt.Errorf("误操作非自己的文章,uid: %d authorId: %d", art.Id, art.AuthorId)
		}
		return tx.Model(&Article{}).
			Where("id=? and author_id=?", art.Id, art.AuthorId).
			Updates(map[string]any{
				"status": art.Status,
				"utime":  now,
			}).Error
	})
}
func (dao *GormArticleDao) Sync(ctx context.Context, art Article) (int64, error) {
	//先操作制作库，后操作线上库
	err := dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var err error
		txDao := NewGormArticleDao(tx)
		if art.Id > 0 {
			err = txDao.UpdateById(ctx, art)
		} else {
			art.Id, err = txDao.Insert(ctx, art)
		}
		if err != nil {
			return err
		}

		//操作线上库,插入/更新：解决并发问题
		return dao.Upsert(ctx, PublishedArticle(art))
	})
	return art.Id, err
}
func (dao *GormArticleDao) Upsert(ctx context.Context, art PublishedArticle) error {
	now := time.Now().UnixMilli()
	art.Ctime = now
	art.Utime = now
	return dao.db.WithContext(ctx).Clauses(clause.OnConflict{
		//二选一
		//DoNothing:
		//mysql只需要关心这里
		DoUpdates: clause.Assignments(map[string]interface{}{
			"title":   art.Title,
			"content": art.Content,
			"utime":   now,
			"status":  art.Status,
		}),
	}).Create(&art).Error
	//插入或在冲突时更新记录
}
func (dao *GormArticleDao) GetByAuthor(ctx context.Context, author int64, offset, limit int) ([]Article, error) {
	var arts []Article
	err := dao.db.WithContext(ctx).Model(&Article{}).Where("author_id=?", author).
		Offset(offset).
		Limit(limit).
		Order("utime desc").
		Find(&arts).Error
	return arts, err
}
func (dao *GormArticleDao) GetById(ctx context.Context, id int64) (Article, error) {
	var art Article
	err := dao.db.WithContext(ctx).Where("id=?", id).First(&art).Error
	return art, err
}
func (dao *GormArticleDao) GetPublishById(ctx context.Context, id int64) (PublishedArticle,
	error) {
	var art PublishedArticle
	err := dao.db.WithContext(ctx).Where("id=?", id).First(&art).Error
	return art, err
}
func (dao *GormArticleDao) ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]PublishedArticle, error) {
	var art []PublishedArticle
	err := dao.db.WithContext(ctx).Where("utime<?", start.UnixMilli()).Order("utime desc").
		Limit(limit).Offset(offset).Find(&art).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return []PublishedArticle{}, nil
	}
	return art, err
}
